/**
 * RACPAlgo class 
 * adapted from the work of Mingyan Liu and Chih-fan Hsin
 * 
 * @created 2007-04-25
 * @author adegomme
 */

package sleeping;

import objects.*;
import kernel.*;
import java.lang.Math;
import java.util.*;
import java.awt.event.*;
import java.awt.*;



public class RACPAlgo extends SleepingAlgo{

    // ***** These constants can be modified in order to test new values for the algorithm : *******
	
    //standard value that will be used in the timers' calculation, constant for each sensor class
    public static final int M	=	6;		//default 6
    //standard value that will be used in the timers' calculation, affects the timer differently for each sensor class
    public static final int V	=	5;		//default 5
	
    //the maximum sleep duration (sensors with low energy will be able to sleep at least MAXSLEEP-T3MAX = 50 seconds)
    public static final int MAXSLEEP  =	60;	//default 60
	
    //These values are the minimal and maximal timers for each sensor class
	
    public static int T1MIN	=	M+3*V  ;	//default M+3*V        21
    public static int T1MAX	=	M+5*V-1 ;	//default M+5*V-1    30
	
    public static int T2MIN	=	M+V ;		//default M+V            11
    public static int T2MAX	=	M+3*V-1 ;	//default M+3*V-1    20
	
    public static int T3MIN	=	M-V ;		//default M-V            1
    public static int T3MAX	=	M+V-1 ;		//default M+V-1        10
	
    /*example : for the class 1 (the one with the biggest energy remaining) : the timer is uniformaly distributed 
      between M+3*V and M+5*V-1  (21 and 30 seconds in the default simulation)
      The sleeping time is also calculated with those constants
    */
	
	
    //These values are the sizes of the different communications, used for the consumption's calculation
	
    //Tr : transmission rate, in b/s
    public static final double Tr	=	20000; //default 20
    //Pcorlg	: length of the hello package, sent once a second
    public static final double Pcorlg	=	13; //default 13
    //Pheadlg	: length of the package sent by the eligible station to warn its neighbours
    public static final double Pheadlg	=	46;	//default 46
    //Packlg	: length of the package sent by the sponsors to answer the Head message
    public static final double Packlg	=	19;	//default 19
	
    //Th : time between 2 head messages
    public static final int Th	=	1;	//default 1	
    /*************************************************************************************/
	
	
    /** 	internal object used in order to save the state, the timer, and the sets of zones
	/	for each sensor
    */
    private class RACPSensor implements Comparable {
	//The represented sensor
	private Sensor _sens;
	// for each zone covered by the sensor, set of sensors that are also covering it		
	private ArrayList< ArrayList<RACPSensor> > _coveringZonesVect; 
	// set of sensors which k-cover the sensor's measurement area
	private ArrayList<RACPSensor> _coveringSensors; 
	// The sensor's state for the algorithm (HEAD, REGULAR,ELIGIBLE,SPONSOR or DEAD)
	private int _state;
	// The timer for the sleeping
	private float _timer;
	// if true, the measurement zone of the sensor is k-covered by other sensors, and can go in sleeping state
	private boolean _kcovered;

	public RACPSensor(Sensor s){
	    _sens = s;
	    _coveringZonesVect=new ArrayList();
	    _coveringSensors=new ArrayList();
	}
		
	/**
	 * @return the represented sensor
	 */
	public Sensor getSensor(){
	    return _sens;
	}
		
	/**
	 * @param the represented sensor
	 */
	public void setSensor(Sensor s){
	    _sens=s;
	}
		
	/**
	 * @return the state of the sensor
	 */
	public int getState(){
	    return _state;
	}
		
	/**
	 * @param the wanted state
	 */
	public void setState(int state){
	    _state=state;
	}
		
	/**
	 * @return the list of sensors that k-cover the measurement zone of the sensor
	 */
	public ArrayList getCoveringSensors(){
	    return _coveringSensors;
	}
		
	/**
	 * @param the list of sensors that k-cover the measurement zone of the sensor
	 */
	public void setCoveringSensors(ArrayList cs){
	    _coveringSensors=cs;
	}
		
	/**
	 * @return the zones covered by the sensor, and for each one of them, the sensors that are covering them
	 */	
	public ArrayList getCoveringZonesVect(){
	    return _coveringZonesVect;
	}
		
	/**
	 * @param the zones covered by the sensor, and for each one of them, the sensors that are covering them
	 */
	public void setCoveringZonesVect(ArrayList cs){
	    _coveringZonesVect=cs;
	}
		
	/**
	 * @return the timer
	 * if in REGULAR mode, the timer before the sensors becomes ELIGIBLE
	 * if in HEAD mode, the timer before the sensors wakes up
	 */
	public float getTimer(){
	    return _timer;
	}
		
	/**
	 * if in REGULAR mode, the timer before the sensors becomes ELIGIBLE
	 * if in HEAD mode, the timer before the sensors wakes up
	 * @param the timer
	 */
	public void setTimer(float timer){
	    _timer=timer;
	}
		
	/**
	 * @return true if the measurement zone of the sensor is entirely k-covered by others sensors
	 */	
	public boolean getKCovered(){
	    return _kcovered;
	}
		
	/**
	 * @param true if the measurement zone of the sensor is entirely k-covered by others sensors
	 */
	public void setKCovered(boolean k){
	    _kcovered=k;
	}
	
	public String toString(){
	    return _sens.getName();
	}
		
		
	/**  Allows the algorithm to classify sensors by their timer, in order to stick to the reality
	 * @param other other must have RACPSensor type
	 */
	public int compareTo(Object other) { 
	    float nombre1 = ((RACPSensor) other).getTimer(); 
	    float nombre2 = this.getTimer(); 
	    if (nombre1 > nombre2)  return -1; 
	    else if(nombre1 == nombre2) return 0; 
	    else return 1; 
	} 
	


    }
	

    //List that will be used to iterate on the Sensor network's sensors, but ordered by the timer
    private ArrayList<RACPSensor> _sensorList;
	

	
    public static final int REGULAR		=0;//normal sensors, with a timer
    public static final int ELIGIBLE	=1;//sensors which timer has reached 0
    public static final int HEAD		=2;//sleeping sensors, with a timer
    public static final int SPONSOR		=3;//sensors waiting for a neighbour to wake up, with no timer
    public static final int DEAD		=4;//sensors with finished battery

	//static values used in order not to calculate them every loop
	private static double tappel;
	private static double ti;//constant for the head package battery level
	
	
    public RACPAlgo(){
    }

    
    public void setSensorNetwork(SensorNetwork sn){
	_sn = sn;
	_sensorList = new ArrayList();
	//optimization: we assume that all sensors have the same battery level when the algorithm is initialized
	// the time will be the lowest time possible, in order to make the sensors sleep as soon as possible 
	int t1=(T3MAX-T3MIN) + T3MIN;
	float time;
	Random random = new Random();
	Vector v= _sn.getSensorVect();
	for(int i=0;i<v.size();i++){
	    RACPSensor r=new RACPSensor((Sensor)v.get(i));
	    r.setState(REGULAR);
	    r.setTimer(random.nextFloat()*t1);
	    r.setKCovered(false);
	    _sensorList.add(r);
	}
	tappel=_sn.getSleepingPeriod()/1000;
	ti =  tappel*Th*( Pcorlg / Tr);
    }
	

	
    public String getName(){
	return "RACPAlgo";
    }
	
	
    /**
     *	Method that initialize, for a sensor, 
     * 	the list of sensors which are covering same zones as it is
     *	@param the current sensor
     */
    private void initCoveringZones(RACPSensor r){
	int kcover=_sn.getKCover();
	
	
	Sensor sens = r.getSensor();
	ArrayList< ArrayList<RACPSensor> > vect = new ArrayList();

	boolean kc=true;// is the sensor kcovered?
		
	for(int j=sens.getCellMinX(); j<=sens.getCellMaxX()&&kc;j++){
	    for(int k=sens.getCellMinY();k<=sens.getCellMaxY()&&kc;k++){
		//if a zone isn't correctly kcovered by other sensors, the calculation is stopped in order to optimize the speed of the algorithm

		if(_sn.coverCell(sens,j,k)){//we are in a zone the current sensor is covering
		    // we now have to find which are the sensors that are also covering it
					
		    ArrayList<RACPSensor> temp = new ArrayList();
		    vect.add(temp);
		    //the zone is added to the list of the covered zones
				
		    for(int l=0; l<_sensorList.size();l++){
			RACPSensor s=_sensorList.get(l);
			//the current sensor isn't in its own set
			//a sleeping sensor can't be in the set
			if( !(s.equals(r)) &&(!s.getSensor().getSleepingState())&& _sn.coverCell(s.getSensor(),j,k) ){
			    temp.add(s);
			}

		    }
		    // permits to know wether the measurement zone is entirely kcovered or not
		    if(temp.size()<kcover){
			kc=false;
		    }
								
		}
	    }
	}
	r.setKCovered(kc);
	r.setCoveringZonesVect(vect);
    }
	
	
    /**
     * Method that calculates the neighbours which assure the k-coverage in the
     * sensor's measurement zone if this sensor is asleep.
     *  @param the sensor
     */
    private void calcCoveringSensors(RACPSensor r){
	
	int kcover=_sn.getKCover();
	boolean sensorOK=true;
	Sensor sens = r.getSensor();
	r.setCoveringSensors(new ArrayList());
	
	ArrayList< ArrayList<RACPSensor> > vect = r.getCoveringZonesVect();// the zones and the sensors covering those zones
		
	for(int j=0; j<_sensorList.size();j++){//iterates on the sensors in order to check if they can be removed from the final set
	    RACPSensor testedsens= _sensorList.get(j);
	    sensorOK=true;//default case : the sensor can be removed
		
	    if(!testedsens.getSensor().getSleepingState()){//if the sensor is in  a sleeping state, it has to be removed
		
		for(int i =0;i<vect.size();i++){
		    ArrayList temp=vect.get(i);
		    //only checks zones that are covered by the current sensor and by the tested one at once
		    if(temp.contains(testedsens)&&temp.size()<=kcover) {
			//if this sensor is removed, the kcover is disrupted, so it mustn't be removed
			sensorOK=false;
			ArrayList temp3 = r.getCoveringSensors();
			if (!temp3.contains(testedsens)){
			    temp3.add(testedsens);
			}//the sensor is added to the set representing the kcovering sensors for the current one
		    }
		}
	    }			
			
	    //the sensor can now be removed of the coveringzonesvect
	    //else it will become a sponsor for the current sensor
	    if (sensorOK){
		for(int l=0; l<vect.size();l++){
		    vect.get(l).remove(testedsens);
		}
	    }
	}

	
    }	

    /** 
     * Method that calculates the timers for each new REGULAR sensor
     * adapted from the works of Mingyan Liu and Chih-fan Hsin - 
     * This method is to be modified if you want to implement new choices for the timers' calculation
     * @param s the sensor which timer needs to be initiated
     * @return the timer 
     */
    public float calcTimers(RACPSensor r){
	ArrayList<RACPSensor> v = r.getCoveringSensors();
	TreeSet batt = new TreeSet();

	for(RACPSensor sens : v){//building of an ordered set with the neighbours' batteries
	    batt.add(sens.getSensor().getBatteryLevel());
	}
	
	//the number of different batteries is saved (note : a TreeSet doesn't keep twice the same value)
	int taille = batt.size();

	//The current sensor's battery is added to the TreeSet, and its rank is checked
	double battlevel = r.getSensor().getBatteryLevel();
	batt.add(battlevel);
	int rang = batt.headSet(battlevel).size();//dirty coding
	
	
	// The sensor goes in one of the 3 groups
	// the first group is the one with the highest battery levels
	// the third one the one with the lowest
	float g1= taille/3;
	float g2= g1*2 ;
	
	int groupe=0;
	
	if(rang<g1)		groupe = 3;
	else if(rang<g2)groupe = 2;
	else			groupe = 1;
	//We now have the group of the sensor
	
	Random random = new Random();
	float timer;
	int tmin=0;
	int tmax=0;
	
	switch (groupe) {
	case 1:
	    tmin = T1MIN ;  
	    tmax = T1MAX; 
	    break;
	case 2:
	    tmin = T2MIN ;  
	    tmax = T2MAX; 
	    break;
	case 3:
	    tmin = T3MIN ;  
	    tmax = T3MAX; 
	    break;
	default:
	    break;
	}
	
	timer= random.nextFloat()*(tmax-tmin) + tmin;// generates a random float between tmin and tmax
	
	return timer;
    }


    /** 
     * Method that calculates the sleeping length for each new HEAD sensor
     * adapted from the works of Mingyan Liu and Chih-fan Hsin - 
     * This method is to be modified if you want to implement new choices for the timers' calculation
     * @param s the sensor whose timer needs to be initiated
     * @return the timer 
     */
    private float calcSleepingLength(RACPSensor r){
	return (MAXSLEEP - calcTimers(r));
    }


	

    /**
     * Calculates the sleeping for a SensorNetwork
     *
     * @param simulator reference to the simulator
     */
    public void calculateSleeping() {


	//The sensor List is ordered in order to check first the sensors with the lowest timer, more realistic
	Collections.sort(_sensorList);
	for(int i=0;i<_sensorList.size();i++){
			
	    RACPSensor r=_sensorList.get(i);
	    Sensor s = r.getSensor();
	    //battery update after the Pcor package is sent (default: once/second))
		//sleeping sensors don'nt send those packages
	    double batt1 = s.getBatteryLevel();
		int state=r.getState();
	    if(batt1>0){
		batt1 -=(( ti* s.getConsumption()[Station.SPEAKING] )/3600);
		if(batt1<=0&&state!=DEAD){
		    r.setState(DEAD);
		    _sn.setSleeping(s,true);
		    r.setTimer(-1);
		}
		s.setBatteryLevel(batt1);
	    }
		
	    switch(state){
			
	    case(REGULAR)  :

		float c= r.getTimer();
		c-=tappel; 
		if(c>0){//not finished timer, the sensor remains in REGULAR state
		    r.setTimer(c);
		}else{//the timer has finished, the sensor becomes ELIGIBLE, his neighbours become SPONSORS
		    r.setTimer(-1);
		    r.setState(ELIGIBLE);	
		    //battery update after the Phead package is sent 
		    double batt2 = s.getBatteryLevel();
		    batt2 -= (( s.getConsumption()[Station.SPEAKING] * ( Pheadlg / Tr) )/3600);
		    if(batt2<=0){
			r.setState(DEAD);
			_sn.setSleeping(s,true);
			r.setTimer(-1);
		    }
		    s.setBatteryLevel(batt2);
			
		}
		break;

	    case(ELIGIBLE) : // in reality the ELIGIBLE sensor has to wait an answer from his neighbours.
		// in Simmobil, it will just calculate the sleeping time and prepare the sleeping
		// communications will be skipped
							
		initCoveringZones(r);//initializes the zones of the sensor, and the _kcovered boolean
						
		if(r.getKCovered()){//if the k-cover is realized for this sensor by other sensors 
		    //<=> a sufficient ACK number is recieved
					
		    //we can now calculate the set of sensors that really kcover the zone if the current sensor is asleep
		    calcCoveringSensors(r);	
		    //puts the sensor into sleeping state 
		    float t= calcSleepingLength(r);
		    r.setState(HEAD);
		    _sn.setSleeping(s,true);
		    r.setTimer(t);
					
		    //puts the sponsors into sponsor state
		    for(int j=0;j<r.getCoveringSensors().size();j++){
			//finds the place in sensorList of the sponsor
			int k=_sensorList.indexOf(r.getCoveringSensors().get(j));
						
			//battery update after the Pack package is sent 
			RACPSensor r2 = _sensorList.get(k);
			Sensor s2=r2.getSensor();
			double batt3 = s2.getBatteryLevel();
			batt3 -= (( s2.getConsumption()[Station.SPEAKING] * ( Packlg / Tr) )/3600);
			if(batt3<=0){
			    r2.setState(DEAD);
			    //if a sponsor dies during its sponsoring period, the kcover may not be preserved 
			    //The algorithm should privilegiate high battery sensors to be sponsors, but this is not yet implemented
			    _sn.setSleeping(s2,true);
			    r2.setTimer(-1);
			}else {	
			    r2.setState(SPONSOR);
			    r2.setTimer(t);
			}
			s2.setBatteryLevel(batt3);
		    }
					
		}else {	//if the kcover isn't preserved by other sensors, the current one can't sleep
		    r.setState(REGULAR);
		    float t2= calcTimers(r);
		    r.setTimer(t2);
		}
		break;

	    case(HEAD)     : 
				
		float c2= r.getTimer();
		c2 -= tappel;
		if(c2>0){	
		    r.setTimer(c2);
		}else{//the timer has finished, the sensor wakes up, and its sponsors too
					
		    r.setState(REGULAR);	
		    _sn.setSleeping(s,false);
		    float t2= calcTimers(r);
		    r.setTimer(t2);
		}

		break;		
	    case(SPONSOR)  ://a sponsor doesn't sleep for the time the HEAD sensor sleeps
		float c3= r.getTimer();
		c3 -= tappel;					
		if(c3>0){	
		    r.setTimer(c3);
		}else{//the timer has finished, the sponsor can go to regular mode
		    r.setState(REGULAR);	
		    float t3= calcTimers(r);
		    r.setTimer(t3);
		}
						
					
	    case(DEAD)  :
		break;
	    }
	}

    }

    public int getRACPSensorState(int i){
	return ((RACPSensor)_sensorList.get(i)).getState();
    }

    public ArrayList getSensorList(){
	return _sensorList;
    }

}
